#!/usr/bin/env python3
"""
Apple Firmware Signing Status Monitor
监控Apple固件签名状态并在状态变化时发送邮件通知
"""

import os
import sys
import time
import json
import smtplib
import logging
from datetime import datetime
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from pathlib import Path

import requests
from bs4 import BeautifulSoup
from dotenv import load_dotenv


# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('firmware_monitor.log', encoding='utf-8'),
        logging.StreamHandler(sys.stdout)
    ]
)
logger = logging.getLogger(__name__)


class FirmwareMonitor:
    """固件签名状态监控器"""
    
    # 设备名称映射表（appledb.dev格式 ↔ IPSW格式）
    DEVICE_MAPPING = {
        'iPhone-15-Pro-Max': 'iPhone16,2',
        'iPhone-15-Pro': 'iPhone16,1',
        'iPhone-15-Plus': 'iPhone15,5',
        'iPhone-15': 'iPhone15,4',
        'iPhone-14-Pro-Max': 'iPhone15,3',
        'iPhone-14-Pro': 'iPhone15,2',
        'iPhone-14-Plus': 'iPhone14,8',
        'iPhone-14': 'iPhone14,7',
        'iPhone-13-Pro-Max': 'iPhone14,3',
        'iPhone-13-Pro': 'iPhone14,2',
        'iPhone-13': 'iPhone14,5',
        'iPhone-13-mini': 'iPhone14,4',
    }
    
    # 反向映射表（IPSW格式 → appledb.dev格式）
    REVERSE_DEVICE_MAPPING = {v: k for k, v in DEVICE_MAPPING.items()}
    
    def __init__(self):
        """初始化监控器并加载配置"""
        load_dotenv()
        
        # 必需的配置参数
        device_model_raw = os.getenv('DEVICE_MODEL')
        self.device_models = self._parse_device_models(device_model_raw)
        
        # 设备友好名称映射 {设备型号: 友好名称}
        self.device_names = {}
        
        target_version_raw = os.getenv('TARGET_VERSION')
        self.target_versions = self._parse_target_versions(target_version_raw)
        
        # 版本区间配置（可选）
        self.version_min = os.getenv('VERSION_MIN')
        self.version_max = os.getenv('VERSION_MAX')
        
        self.check_interval = int(os.getenv('CHECK_INTERVAL', 1800))
        
        # 邮件配置
        self.smtp_server = os.getenv('SMTP_SERVER')
        self.smtp_port = int(os.getenv('SMTP_PORT', 587))
        self.email_user = os.getenv('EMAIL_USER')
        self.smtp_password = os.getenv('SMTP_PASSWORD')
        
        # 解析接收邮箱列表（支持多个邮箱，用分号或逗号分隔）
        receiver_email_raw = os.getenv('RECEIVER_EMAIL', '')
        self.receiver_emails = self._parse_receiver_emails(receiver_email_raw)
        
        # 状态文件路径
        self.state_file = Path('firmware_status.json')
        
        # 验证必需配置
        self._validate_config()
        
        # 加载上次状态
        self.last_status = self._load_status()
        
        logger.info(f"监控器已初始化")
        logger.info(f"  监控设备数: {len(self.device_models)}")
        logger.info(f"  设备列表: {', '.join(self.device_models)}")
        if self.target_versions:
            logger.info(f"  监控版本数: {len(self.target_versions)}")
            logger.info(f"  版本列表: {', '.join(self.target_versions)}")
        if self.version_min or self.version_max:
            logger.info(f"  版本区间: {self.version_min or '不限'} ~ {self.version_max or '不限'}")
    
    def _normalize_device_model(self, device_model):
        """
        标准化设备型号格式
        支持多种输入格式：
        - iPhone16,2 (IPSW格式) → 直接使用
        - iPhone-15-Pro-Max.html (appledb格式) → 转换为iPhone16,2
        - iPhone-15-Pro-Max → 转换为iPhone16,2
        """
        if not device_model:
            return None
        
        # 移除.html后缀
        device_model = device_model.replace('.html', '')
        
        # 如果已经是正确格式（包含逗号），直接返回
        if ',' in device_model:
            return device_model
        
        # 尝试从映射表转换
        if device_model in self.DEVICE_MAPPING:
            converted = self.DEVICE_MAPPING[device_model]
            logger.info(f"设备标识符已转换: {device_model} → {converted}")
            return converted
        
        # 如果是URL格式，提取设备名称
        if 'appledb.dev/device/' in device_model:
            device_name = device_model.split('/device/')[-1].replace('.html', '')
            if device_name in self.DEVICE_MAPPING:
                converted = self.DEVICE_MAPPING[device_name]
                logger.info(f"设备标识符已转换: {device_name} → {converted}")
                return converted
        
        # 无法转换，返回原值（后续验证会报错）
        logger.warning(f"无法识别的设备格式: {device_model}，将尝试直接使用")
        return device_model
    
    def _parse_device_models(self, device_model_raw):
        """
        解析设备配置，支持多设备
        支持格式：
        - 单设备: iPhone16,2
        - 多设备: iPhone16,2;iPad14,6;iPhone15,3 (使用分号分隔)
        - 混合格式: iPhone-15-Pro-Max.html;iPad14,6
        
        注意：多设备必须使用分号(;)分隔，因为设备标识符本身包含逗号
        """
        if not device_model_raw:
            return []
        
        # 判断是否包含分号（多设备）
        if ';' in device_model_raw:
            # 使用分号分隔多设备
            devices_raw = [d.strip() for d in device_model_raw.split(';')]
        elif ',' in device_model_raw and '-' not in device_model_raw:
            # 如果没有分号但有逗号，且不是HTML格式，可能是旧配置（单设备）
            # iPhone16,2 这种格式保持不变
            devices_raw = [device_model_raw.strip()]
        else:
            # 单设备
            devices_raw = [device_model_raw.strip()]
        
        # 标准化每个设备
        devices = []
        for device in devices_raw:
            if device:
                normalized = self._normalize_device_model(device)
                if normalized:
                    devices.append(normalized)
        
        return devices
    
    def _parse_target_versions(self, target_version_raw):
        """
        解析版本配置，支持多版本
        支持格式：
        - 单版本: 18.0
        - 多版本: 17.6.1;18.0;18.1 (推荐使用分号)
        - 多版本: 17.6.1,18.0,18.1 (逗号也支持)
        """
        if not target_version_raw:
            return []
        
        # 分割多个版本（优先使用分号，兼容逗号）
        if ';' in target_version_raw:
            versions = [v.strip() for v in target_version_raw.split(';')]
        else:
            versions = [v.strip() for v in target_version_raw.split(',')]
        
        return [v for v in versions if v]
    
    def _parse_receiver_emails(self, receiver_email_raw):
        """解析接收邮箱列表"""
        if not receiver_email_raw:
            return []
        
        # 支持分号或逗号分隔
        if ';' in receiver_email_raw:
            emails = [e.strip() for e in receiver_email_raw.split(';')]
        else:
            emails = [e.strip() for e in receiver_email_raw.split(',')]
        
        # 过滤掉空字符串和示例邮箱
        valid_emails = []
        for email in emails:
            if email and '@' in email and 'your' not in email.lower():
                valid_emails.append(email)
        
        return valid_emails
    
    def _convert_to_appledb_format(self, device_model):
        """
        将IPSW格式的设备标识符转换为appledb.dev的HTML格式
        iPhone16,2 → iPhone-15-Pro-Max.html
        """
        # 如果已经是HTML格式，直接返回
        if '-' in device_model and not ',' in device_model:
            # 确保有.html后缀
            if not device_model.endswith('.html'):
                return device_model + '.html'
            return device_model
        
        # 使用反向映射表转换
        if device_model in self.REVERSE_DEVICE_MAPPING:
            html_name = self.REVERSE_DEVICE_MAPPING[device_model]
            result = f"{html_name}.html"
            logger.debug(f"转换为appledb格式: {device_model} → {result}")
            return result
        
        # 无法转换，尝试猜测格式
        # 对于未知设备，尝试从API获取名称
        logger.warning(f"无法将 {device_model} 转换为appledb.dev格式")
        return device_model
    
    def _validate_config(self):
        """验证必需的配置参数"""
        # 验证设备配置
        if not self.device_models:
            raise ValueError("缺少必需的配置参数: DEVICE_MODEL")
        
        # 验证版本配置（至少需要指定版本或版本区间）
        if not self.target_versions and not self.version_min and not self.version_max:
            raise ValueError("必须指定 TARGET_VERSION 或 VERSION_MIN/VERSION_MAX")
        
        # 验证邮件配置
        required_configs = {
            'SMTP_SERVER': self.smtp_server,
            'EMAIL_USER': self.email_user,
            'SMTP_PASSWORD': self.smtp_password
        }
        
        missing = [key for key, value in required_configs.items() if not value]
        if missing:
            raise ValueError(f"缺少必需的配置参数: {', '.join(missing)}")
        
        # 验证接收邮箱
        if not self.receiver_emails:
            raise ValueError("缺少必需的配置参数: RECEIVER_EMAIL（需要至少一个有效邮箱）")
    
    def _load_status(self):
        """加载上次保存的签名状态"""
        if self.state_file.exists():
            try:
                with open(self.state_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    logger.info(f"已加载上次状态: {data}")
                    return data
            except Exception as e:
                logger.warning(f"加载状态文件失败: {e}")
        return {}
    
    def _save_status(self, status_data):
        """保存当前签名状态"""
        try:
            with open(self.state_file, 'w', encoding='utf-8') as f:
                json.dump(status_data, f, ensure_ascii=False, indent=2)
            logger.debug(f"状态已保存: {status_data}")
        except Exception as e:
            logger.error(f"保存状态文件失败: {e}")
    
    def check_signing_status(self):
        """
        检查所有设备和版本的固件签名状态
        返回: [(device, version, is_signed, firmware_info), ...] 列表
        """
        results = []
        
        logger.info("=" * 70)
        logger.info(f"开始检查 {len(self.device_models)} 个设备的固件签名状态")
        logger.info("=" * 70)
        
        for idx, device in enumerate(self.device_models, 1):
            # 获取该设备的所有固件
            firmwares = self._get_device_firmwares(device)
            
            if not firmwares:
                logger.warning(f"  [ERROR] 设备 {device} 未找到固件信息（API可能失败或设备型号错误）")
                continue
            
            # 获取设备友好名称
            device_name = firmwares[0].get('device_name', device) if firmwares else device
            self.device_names[device] = device_name
            
            logger.info(f"\n[设备 {idx}/{len(self.device_models)}] 正在检查: {device} ({device_name})")
            logger.info(f"  [OK] 从API获取到 {len(firmwares)} 个固件版本")
            
            # 检查目标版本
            matched_count = 0
            opened_count = 0
            closed_count = 0
            
            for fw in firmwares:
                version = fw.get('version', '')
                is_signed = fw.get('signed', False)
                build = fw.get('build', 'N/A')
                
                # 检查是否匹配目标版本或版本区间
                if self._should_monitor_version(version):
                    matched_count += 1
                    
                    if is_signed:  # 签名已开放
                        opened_count += 1
                        results.append((device, version, is_signed, fw))
                        logger.info(f"  [YES] {version} (Build: {build}) - 签名已开放 ***")
                    else:  # 签名已关闭
                        closed_count += 1
                        logger.info(f"  [NO]  {version} (Build: {build}) - 签名已关闭")
            
            # 总结该设备的检查结果
            device_display = f"{device} ({self.device_names.get(device, device)})" if device in self.device_names else device
            logger.info(f"\n  [设备总结] {device_display}:")
            logger.info(f"     - 匹配目标版本: {matched_count} 个")
            logger.info(f"     - 签名已开放: {opened_count} 个")
            logger.info(f"     - 签名已关闭: {closed_count} 个")
            
            if matched_count == 0:
                logger.warning(f"     [警告] 未找到匹配的目标版本！")
                logger.info(f"     [提示] 检查 TARGET_VERSION 配置是否正确")
        
        logger.info("\n" + "=" * 70)
        logger.info(f"检查完成: 共发现 {len(results)} 个已开放签名的固件版本")
        logger.info("=" * 70)
        
        return results
    
    def _get_device_firmwares(self, device):
        """获取指定设备的所有固件列表"""
        # 优先使用IPSW.me API
        logger.debug(f"  → 数据源1: 尝试 IPSW.me API")
        firmwares = self._get_firmwares_from_ipsw(device)
        
        if firmwares:
            logger.info(f"  [数据源] IPSW.me API (OK)")
            return firmwares
        
        # 备用：尝试appledb.dev API
        logger.warning(f"  [数据源] IPSW.me失败，尝试 AppleDB.dev API")
        firmwares = self._get_firmwares_from_appledb(device)
        
        if firmwares:
            logger.info(f"  [数据源] AppleDB.dev API (OK)")
        else:
            logger.error(f"  [数据源] 所有数据源均失败")
        
        return firmwares
    
    def _get_firmwares_from_ipsw(self, device):
        """从IPSW.me获取固件列表"""
        url = f"https://api.ipsw.me/v4/device/{device}?type=ipsw"
        
        try:
            logger.debug(f"     请求: {url}")
            response = requests.get(url, timeout=30)
            response.raise_for_status()
            
            data = response.json()
            device_name = data.get('name', device)
            firmwares_data = data.get('firmwares', [])
            
            logger.debug(f"     设备名称: {device_name}")
            logger.debug(f"     固件总数: {len(firmwares_data)}")
            
            # 转换为统一格式
            firmwares = []
            for fw in firmwares_data:
                firmwares.append({
                    'version': fw.get('version', ''),
                    'build': fw.get('buildid', 'N/A'),
                    'signed': fw.get('signed', False),
                    'release_date': fw.get('releasedate', 'N/A'),
                    'url': fw.get('url', 'N/A'),
                    'device_name': device_name
                })
            
            return firmwares
            
        except requests.HTTPError as e:
            logger.error(f"     IPSW API HTTP错误 {e.response.status_code}: {e}")
            if e.response.status_code == 404:
                logger.error(f"     设备型号 {device} 不存在，请检查配置")
        except requests.RequestException as e:
            logger.error(f"     IPSW API网络错误: {e}")
        except Exception as e:
            logger.error(f"     IPSW API未知错误: {e}")
        
        return []
    
    def _get_firmwares_from_appledb(self, device):
        """从appledb.dev获取固件列表（备用）"""
        # appledb.dev是Vue.js应用，无法直接抓取
        # 这里返回空，主要依赖IPSW API
        return []
    
    def _should_monitor_version(self, version):
        """判断是否应该监控此版本"""
        if not version:
            return False
        
        # 如果指定了具体版本列表
        if self.target_versions:
            return version in self.target_versions
        
        # 如果指定了版本区间
        if self.version_min or self.version_max:
            return self._in_version_range(version)
        
        return False
    
    def _in_version_range(self, version):
        """检查版本是否在指定区间内"""
        try:
            from packaging import version as pkg_version
            ver = pkg_version.parse(version)
            
            if self.version_min:
                min_ver = pkg_version.parse(self.version_min)
                if ver < min_ver:
                    return False
            
            if self.version_max:
                max_ver = pkg_version.parse(self.version_max)
                if ver > max_ver:
                    return False
            
            return True
        except:
            # 如果解析失败，使用简单的字符串比较
            if self.version_min and version < self.version_min:
                return False
            if self.version_max and version > self.version_max:
                return False
            return True
    
    def _check_ipsw_api(self):
        """
        使用IPSW.me API检查签名状态
        """
        url = f"https://api.ipsw.me/v4/device/{self.device_model}?type=ipsw"
        
        try:
            logger.info(f"正在检查 {url}")
            
            response = requests.get(url, timeout=30)
            response.raise_for_status()
            
            data = response.json()
            device_name = data.get('name', self.device_model)
            logger.info(f"设备: {device_name}")
            
            if 'firmwares' not in data:
                logger.warning("API响应中没有固件列表")
                return None, None
            
            firmwares = data['firmwares']
            logger.debug(f"找到 {len(firmwares)} 个固件")
            
            # 查找目标版本
            for fw in firmwares:
                version = fw.get('version', '')
                if self.target_version == version or self.target_version in version:
                    is_signed = fw.get('signed', False)
                    firmware_info = {
                        'version': version,
                        'build': fw.get('buildid', 'N/A'),
                        'signed': is_signed,
                        'release_date': fw.get('releasedate', 'N/A'),
                        'url': fw.get('url', 'N/A')
                    }
                    
                    logger.info(f"版本 {version} (Build: {firmware_info['build']}) 签名状态: {'已开放' if is_signed else '已关闭'}")
                    return is_signed, firmware_info
            
            logger.warning(f"未找到版本 {self.target_version}")
            # 显示最新的几个版本供参考
            if firmwares:
                logger.info("最新的3个固件版本:")
                for fw in firmwares[:3]:
                    v = fw.get('version', 'N/A')
                    b = fw.get('buildid', 'N/A')
                    s = '已开放' if fw.get('signed', False) else '已关闭'
                    logger.info(f"  - {v} ({b}) - {s}")
            
            return None, None
                
        except requests.RequestException as e:
            logger.error(f"IPSW API请求失败: {e}")
            return None, None
        except Exception as e:
            logger.error(f"IPSW API检查出错: {e}", exc_info=True)
            return None, None
    
    def _check_appledb_api(self):
        """
        使用appledb.dev API检查签名状态（备用方法）
        appledb.dev 需要使用 HTML 格式的设备名称
        """
        # 将 iPhone16,2 格式转换为 iPhone-15-Pro-Max.html 格式
        device_html = self._convert_to_appledb_format(self.device_model)
        url = f"https://appledb.dev/device/{device_html}"
        
        try:
            logger.info(f"正在检查 {url} (设备: {self.device_model})")
            
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
            }
            
            response = requests.get(url, headers=headers, timeout=30)
            response.raise_for_status()
            
            soup = BeautifulSoup(response.text, 'html.parser')
            
            # 查找目标版本
            firmware_info = self._parse_firmware_info(soup)
            
            if self.target_version in firmware_info:
                is_signed = firmware_info[self.target_version]['signed']
                logger.info(f"版本 {self.target_version} 签名状态: {'已开放' if is_signed else '已关闭'}")
                return is_signed, firmware_info[self.target_version]
            else:
                logger.warning(f"未找到版本 {self.target_version}")
                return None, None
                
        except requests.RequestException as e:
            logger.error(f"网络请求失败: {e}")
            return None, None
        except Exception as e:
            logger.error(f"检查签名状态时出错: {e}", exc_info=True)
            return None, None
    
    def _parse_firmware_info(self, soup):
        """
        解析页面中的固件信息
        返回: {version: {signed: bool, release_date: str, build: str}}
        """
        firmware_dict = {}
        
        try:
            # 查找固件版本表格或列表
            # appledb.dev使用特定的HTML结构，需要查找版本信息
            
            # 方法1: 查找包含版本信息的表格行
            firmware_rows = soup.find_all('tr', class_=['ios-row', 'firmware-row'])
            
            if not firmware_rows:
                # 方法2: 查找所有包含版本号的元素
                firmware_rows = soup.find_all(['div', 'tr'], class_=lambda x: x and 'firmware' in str(x).lower())
            
            for row in firmware_rows:
                try:
                    # 提取版本号
                    version_elem = row.find(['td', 'span', 'div'], class_=lambda x: x and 'version' in str(x).lower())
                    if not version_elem:
                        version_elem = row.find(string=lambda text: text and '.' in text and any(char.isdigit() for char in text))
                    
                    if version_elem:
                        version = version_elem.get_text(strip=True) if hasattr(version_elem, 'get_text') else str(version_elem).strip()
                        
                        # 检查签名状态（查找status-signed类或相关指示器）
                        is_signed = False
                        if row.find(class_='status-signed') or row.find(class_='signed'):
                            is_signed = True
                        elif row.find(class_='status-unsigned') or row.find(class_='unsigned'):
                            is_signed = False
                        else:
                            # 查找包含"signing"或"signed"文本的元素
                            status_text = row.get_text().lower()
                            is_signed = 'signing' in status_text or 'signed' in status_text
                        
                        # 提取build号
                        build_elem = row.find(string=lambda text: text and '(' in text and ')' in text)
                        build = build_elem.strip() if build_elem else 'N/A'
                        
                        firmware_dict[version] = {
                            'signed': is_signed,
                            'build': build,
                            'raw_html': str(row)[:200]  # 保存部分HTML用于调试
                        }
                
                except Exception as e:
                    logger.debug(f"解析单个固件行时出错: {e}")
                    continue
            
            # 如果上述方法未找到信息，尝试使用API或其他方法
            if not firmware_dict:
                logger.warning("未能从页面解析固件信息，尝试备用方法")
                firmware_dict = self._try_api_method()
        
        except Exception as e:
            logger.error(f"解析固件信息失败: {e}", exc_info=True)
        
        return firmware_dict
    
    def _try_api_method(self):
        """尝试使用API方法获取固件信息"""
        try:
            # appledb.dev可能提供API端点
            api_url = f"https://api.appledb.dev/device/{self.device_model}.json"
            response = requests.get(api_url, timeout=15)
            
            if response.status_code == 200:
                data = response.json()
                firmware_dict = {}
                
                # 解析API返回的数据结构
                if 'ios' in data:
                    for ios_entry in data['ios']:
                        version = ios_entry.get('version', '')
                        if version:
                            firmware_dict[version] = {
                                'signed': ios_entry.get('signed', False),
                                'build': ios_entry.get('build', 'N/A')
                            }
                
                return firmware_dict
        except Exception as e:
            logger.debug(f"API方法失败: {e}")
        
        return {}
    
    def send_notification(self, results):
        """
        发送邮件通知
        :param results: [(device, version, is_signed, firmware_info), ...] 列表
        """
        try:
            if not results:
                logger.info("没有需要通知的签名开放")
                return False
            
            # 构建邮件主题
            if len(results) == 1:
                device, version, _, fw_info = results[0]
                device_name = fw_info.get('device_name', self.device_names.get(device, device))
                subject = f"✅ Apple固件签名已开放 - {device_name} {version}"
            else:
                subject = f"✅ Apple固件签名已开放 - {len(results)}个版本"
            
            # 构建邮件正文
            body_parts = ["Apple固件签名状态监控通知\n"]
            body_parts.append(f"发现 {len(results)} 个已开放签名的固件版本：\n")
            body_parts.append("=" * 60 + "\n\n")
            
            for device, version, is_signed, fw_info in results:
                # 优先使用友好名称
                device_name = fw_info.get('device_name', self.device_names.get(device, device))
                
                body_parts.append(f"设备名称: {device_name}")
                body_parts.append(f"设备型号: {device}")
                body_parts.append(f"固件版本: {version}")
                body_parts.append(f"签名状态: ✅ 已开放")
                body_parts.append(f"Build: {fw_info.get('build', 'N/A')}")
                body_parts.append(f"发布日期: {fw_info.get('release_date', 'N/A')}")
                
                # 添加固件下载链接
                firmware_url = fw_info.get('url', '')
                if firmware_url and firmware_url != 'N/A':
                    body_parts.append(f"\n📥 固件下载:")
                    body_parts.append(f"   {firmware_url}")
                
                # 添加IPSW.me直达链接
                ipsw_page_url = f"https://ipsw.me/product/{device}"
                body_parts.append(f"\n🔗 查看所有版本:")
                body_parts.append(f"   {ipsw_page_url}")
                
                body_parts.append("\n" + "-" * 60 + "\n\n")
            
            body_parts.append("⚠️ 重要提示：")
            body_parts.append("1. 现在可以升级或降级到这些版本！")
            body_parts.append("2. 请尽快操作，签名窗口通常很短（几小时到几天）")
            body_parts.append("3. 下载固件后，使用iTunes/爱思助手等工具刷机")
            body_parts.append("4. 刷机前请务必备份重要数据\n\n")
            body_parts.append(f"检查时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            body_parts.append(f"数据来源: https://api.ipsw.me/")
            body_parts.append(f"下载工具: https://ipsw.me/ (官方固件下载站)\n\n")
            body_parts.append("---")
            body_parts.append("此邮件由固件监控系统自动发送")
            
            body = "\n".join(body_parts)
            
            msg = MIMEMultipart()
            msg['From'] = self.email_user
            msg['To'] = ', '.join(self.receiver_emails)  # 多个邮箱用逗号分隔
            msg['Subject'] = subject
            
            msg.attach(MIMEText(body, 'plain', 'utf-8'))
            
            # 发送邮件 - 逐个发送更稳定
            success_count = 0
            fail_count = 0
            
            for receiver_email in self.receiver_emails:
                server = None
                try:
                    # 为每个收件人创建新的消息和连接
                    individual_msg = MIMEMultipart()
                    individual_msg['From'] = self.email_user
                    individual_msg['To'] = receiver_email
                    individual_msg['Subject'] = subject
                    individual_msg.attach(MIMEText(body, 'plain', 'utf-8'))
                    
                    # 建立连接
                    server = smtplib.SMTP(self.smtp_server, self.smtp_port)
                    server.set_debuglevel(0)  # 关闭调试输出
                    server.starttls()
                    server.login(self.email_user, self.smtp_password)
                    
                    # 发送邮件
                    server.send_message(individual_msg)
                    
                    logger.info(f"  [OK] {receiver_email}")
                    success_count += 1
                    
                except Exception as e:
                    logger.error(f"  [FAIL] {receiver_email}: {str(e)}")
                    fail_count += 1
                    
                finally:
                    # 安全关闭连接
                    if server:
                        try:
                            server.quit()
                        except:
                            pass
            
            logger.info(f"发送完成: 成功 {success_count} 个, 失败 {fail_count} 个")
            return success_count > 0
            
        except Exception as e:
            logger.error(f"发送邮件失败: {e}", exc_info=True)
            return False
    
    def should_notify(self, results):
        """
        判断是否需要发送通知
        只在签名开放时通知，签名关闭时不通知
        :param results: 当前检查到的开放签名列表
        :return: 需要通知的结果列表
        """
        if not results:
            logger.info("没有发现开放的签名")
            return []
        
        # 构建当前状态的key集合
        current_keys = set()
        for device, version, is_signed, _ in results:
            current_keys.add(f"{device}|{version}")
        
        # 获取上次的状态
        last_keys = set()
        if self.last_status and 'opened_firmwares' in self.last_status:
            last_keys = set(self.last_status['opened_firmwares'])
        
        # 找出新开放的签名（之前没有，现在有）
        new_opened = current_keys - last_keys
        
        if not new_opened:
            logger.info("签名状态未变化，无需通知")
            return []
        
        # 筛选出需要通知的结果
        notify_results = []
        for device, version, is_signed, fw_info in results:
            key = f"{device}|{version}"
            if key in new_opened:
                notify_results.append((device, version, is_signed, fw_info))
                logger.info(f"新开放签名: {device} - {version}")
        
        return notify_results
    
    def run_once(self):
        """执行一次检查"""
        logger.info("\n" + "=" * 70)
        logger.info(">>> 开始检查固件签名状态")
        logger.info("=" * 70)
        
        # 显示监控配置
        logger.info(f"[监控设备] {len(self.device_models)} 个")
        for idx, device in enumerate(self.device_models, 1):
            device_name = self.device_names.get(device, device)
            if device_name != device:
                logger.info(f"   {idx}. {device} ({device_name})")
            else:
                logger.info(f"   {idx}. {device}")
        
        if self.target_versions:
            logger.info(f"[目标版本] {len(self.target_versions)} 个")
            for version in self.target_versions:
                logger.info(f"   - {version}")
        elif self.version_min or self.version_max:
            logger.info(f"[版本区间]")
            if self.version_min:
                logger.info(f"   - 最小版本: {self.version_min}")
            if self.version_max:
                logger.info(f"   - 最大版本: {self.version_max}")
        
        # 检查所有设备和版本
        results = self.check_signing_status()
        
        # 显示检查结果摘要
        logger.info("\n" + "=" * 70)
        logger.info("[检查结果摘要]")
        logger.info("=" * 70)
        if not results:
            logger.warning(">>> 未发现开放的签名版本")
            logger.info("    所有监控的固件版本签名都已关闭，或未找到匹配的版本")
        else:
            logger.info(f">>> 发现 {len(results)} 个已开放签名的固件版本:")
            for device, version, _, fw_info in results:
                device_display = self.device_names.get(device, device)
                logger.info(f"    [*] {device_display} - {version} (Build: {fw_info.get('build', 'N/A')})")
        logger.info("=" * 70)
        
        # 判断是否需要通知
        notify_results = self.should_notify(results)
        
        if notify_results:
            logger.info(f"\n[邮件通知] 准备发送通知: {len(notify_results)} 个新开放的签名")
            self.send_notification(notify_results)
        else:
            logger.info("\n[邮件通知] 没有发现新开放的签名，无需通知")
        
        # 更新并保存状态
        opened_firmwares = []
        for device, version, _, _ in results:
            opened_firmwares.append(f"{device}|{version}")
        
        current_status = {
            'opened_firmwares': opened_firmwares,
            'last_check': datetime.now().isoformat(),
            'device_count': len(self.device_models),
            'opened_count': len(results)
        }
        self._save_status(current_status)
        self.last_status = current_status
        
        logger.info(f"\n>>> 检查完成于: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
    
    def run_loop(self):
        """循环检查模式"""
        logger.info(f"监控器启动，检查间隔: {self.check_interval}秒")
        
        while True:
            try:
                self.run_once()
                logger.info(f"等待 {self.check_interval} 秒后进行下次检查...")
                time.sleep(self.check_interval)
                
            except KeyboardInterrupt:
                logger.info("收到中断信号，正在退出...")
                break
            except Exception as e:
                logger.error(f"运行时错误: {e}", exc_info=True)
                logger.info("等待60秒后重试...")
                time.sleep(60)


def main():
    """主函数"""
    try:
        monitor = FirmwareMonitor()
        
        # 检查命令行参数
        if len(sys.argv) > 1 and sys.argv[1] == '--once':
            # 单次检查模式
            logger.info("单次检查模式")
            monitor.run_once()
        else:
            # 循环检查模式
            monitor.run_loop()
            
    except ValueError as e:
        logger.error(f"配置错误: {e}")
        sys.exit(1)
    except Exception as e:
        logger.error(f"启动失败: {e}", exc_info=True)
        sys.exit(1)


if __name__ == '__main__':
    main()

